#include <stdio.h>
#include <stdlib.h>
#include <iterator>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <ctime>
#include <cmath>
#include <limits>
#include <mpi.h>
#include <time.h>
#include "IVCalculator.h"
#define Pi 3.141592653589793238462643
#define NUM_IV 24

/* The number of processors in use. */
int procs;

/* Our process id. */
int procId;

clock_t curt;
class CVSRow
{
public:
  std::string const& operator[](std::size_t index) const
  {
    return m_data[index];
  }

  std::size_t size() const
  {
    return m_data.size();
  }

  void readNextRow(std::istream& str)
  {
    std::string         line;
    std::getline(str,line);

    std::stringstream   lineStream(line);
    std::string         cell;

    m_data.clear();
    while(std::getline(lineStream,cell,','))
      {
        m_data.push_back(cell);
      }
  }

private:
  std::vector<std::string>    m_data;
};


std::istream& operator>>(std::istream& str, CVSRow& data)
{
  data.readNextRow(str);
  return str;
}
double CND( double X )
{

  double L, K, w ;

  double const a1 = 0.31938153, a2 = -0.356563782, a3 = 1.781477937;
  double const a4 = -1.821255978, a5 = 1.330274429;

  L = fabs(X);
  K = 1.0 / (1.0 + 0.2316419 * L);
  w = 1.0 - 1.0 / sqrt(2 * Pi) * exp(-L *L / 2) * (a1 * K + a2 * K *K + a3 * pow(K,3) + a4 * pow(K,4) + a5 * pow(K,5));

  if (X < 0 ){
    w= 1.0 - w;
  }
  return w;
}

// The Black and Scholes (1973) Stock option formula
double BlackScholes(char CallPutFlag, double S, double X, double T, double r, double v)
{
  double d1, d2;

  d1=(log(S/X)+(r+v*v/2)*T)/(v*sqrt(T));
  d2=d1-v*sqrt(T);

  if(CallPutFlag == 'c')
    return S *CND(d1)-X * exp(-r*T)*CND(d2);
  else
    return X * exp(-r * T) * CND(-d2) - S * CND(-d1);
}

int julian_cpu(int year, int month, int day) {
  int a = (14 - month) / 12;
  int y = year + 4800 - a;
  int m = month + 12 * a - 3;
  if (year > 1582 || (year == 1582 && month > 10) || (year == 1582 && month == 10 && day >= 15))
    return day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045;
  else
    return day + (153 * m + 2) / 5 + 365 * y + y / 4 - 32083;
}

int dateDiff(int year1, int month1){
  int day1 = 30;
  time_t t = time(0);
  struct tm * now = localtime(&t);

  int year2 = now->tm_year + 1900;
  int month2 = now->tm_mon + 1;
  int day2 = now->tm_mday;
  int julian_day1, julian_day2;
  int difference;

  julian_day1 = julian_cpu(year1, month1, day1);
  julian_day2 = julian_cpu(year2, month2, day2);

  difference = abs(julian_day2 - julian_day1);

  return difference;
}

MPI_Datatype mpi_solution_type = MPI_DATATYPE_NULL;

static void init_mpi_solution_type() {
  MPI_Datatype field_types[] = { MPI_CHAR, MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE, MPI_UNSIGNED, MPI_UNSIGNED, MPI_UNSIGNED };
  int field_lengths[] = { 1,1,1,1,1,1,1 };
  MPI_Aint field_offsets[] = { 0, 0,0,0,0,0,0 };
  size_t num_fields = 7;

  int err = MPI_Type_create_struct(num_fields,
                                   field_lengths,
                                   field_offsets,
                                   field_types,
                                   &mpi_solution_type);

  err = MPI_Type_commit(&mpi_solution_type);

  int mpi_struct_size;

}

void parse_row_req(request &req, CVSRow row)
{
  req.type = atoi(row[0].c_str()); // default
  req.symbol = row[1].c_str();
  req.strike = atof(row[2].c_str());
  req.year = atoi(row[3].c_str()) + 2000;

  if (row[4] == "Jan")
    req.month = 1;
  else if (row[4] == "Feb")
    req.month = 2;
  else if (row[4] == "Mar")
    req.month = 3;
  else if (row[4] == "Apr")
    req.month = 4;
  else if (row[4] == "May")
    req.month = 5;
  else if (row[4] == "Jun")
    req.month = 6;
  else if (row[4] == "Jul")
    req.month = 7;
  else if (row[4] == "Aug")
    req.month = 8;
  else if (row[4] == "Sep")
    req.month = 9;
  else if (row[4] == "Oct")
    req.month = 10;
  else if (row[4] == "Nov")
    req.month = 11;
  else if (row[4] == "Dec")
    req.month = 12;
  else
    req.month = 0;
}
void extract_requests(std::vector<request> * requests)
{
  std::string filename = "request_list.txt";
  std::ifstream file(filename.c_str());

  request req;

  CVSRow row;
  while(file >> row)
    {
      parse_row_req(req, row);
      requests->push_back(req);
    }
}


void parse_row_opt(option &opt, CVSRow row) {

  opt.strike = atof(row[2].c_str());
  opt.bid = atof(row[4].c_str());
  opt.ask = atof(row[5].c_str());
  opt.year = atoi(row[0].c_str()) + 2000;

  if (row[1] == "Jan")
    opt.month = 1;
  else if (row[1] == "Feb")
    opt.month = 2;
  else if (row[1] == "Mar")
    opt.month = 3;
  else if (row[1] == "Apr")
    opt.month = 4;
  else if (row[1] == "May")
    opt.month = 5;
  else if (row[1] == "Jun")
    opt.month = 6;
  else if (row[1] == "Jul")
    opt.month = 7;
  else if (row[1] == "Aug")
    opt.month = 8;
  else if (row[1] == "Sep")
    opt.month = 9;
  else if (row[1] == "Oct")
    opt.month = 10;
  else if (row[1] == "Nov")
    opt.month = 11;
  else if (row[1] == "Dec")
    opt.month = 12;
  else
    opt.month = 0;

  if (opt.month == 1 || opt.month == 3 || opt.month == 5 ||
      opt.month == 7 || opt.month == 8 || opt.month == 10 || opt.month == 12)
    opt.day = 31;
  else if (opt.month == 2) {
    if (opt.year%4 == 0) {
      opt.day = 29;
    }
    else {
      opt.day = 28;
    }
  }
  else {
    opt.day = 30;
  }
}

void extract_options(std::string symbol, int type, std::vector<option> * options, double spot, double risk)
{
  MPI_Request req;
  std::string filename;
  float *IV_result;
  char proc_id[2];
  sprintf(proc_id,"%d",procId);
  int target = 1;
  if (type == 1) {
    filename = "../input/" + symbol + "_call" + proc_id + ".csv";
  }
  else {
    filename = "../input/" + symbol + "_put.csv";
  }

  std::ifstream file(filename.c_str());
  option opt;
  int count = 0;
  CVSRow row;
  while(file >> row)
   {
      opt.type = type;
      parse_row_opt(opt, row);
      options->push_back(opt);
   }
   time_t t = time(0);
   struct tm * now = localtime(&t);
   printf("%d : %d\n",procId,options->size());
   int curr_year = now->tm_year + 1900;
   int curr_month = now->tm_mon + 1;
   int curr_day = now->tm_mday;
   printf("IO : %f\n",(double)(clock() - curt)/CLOCKS_PER_SEC);
   IVCalculator * IVC;
   curt = clock();

   IVC = new IVCalculator();
   IVC->loadOptionData(options,options->size(), spot, risk, curr_year, curr_month, curr_day);
   IVC->setup();
   IVC->calculateIV();
   IV_result=IVC->getResultIV();
   MPI_Send(IV_result,NUM_IV,MPI_FLOAT,0,1,MPI_COMM_WORLD);//should be procId
   delete IVC;
}
void worker(std::string symbol, double spot, double risk)
{
  // extracted vector data
  std::vector<option> options;

  extract_options(symbol, 1, &options, spot, risk);
}

int hashing(int year, int month)
{
  int index;

  time_t t = time(0);
  struct tm * now = localtime(&t);

  int curr_year = now->tm_year + 1900;
  int curr_month = now->tm_mon + 1;
  int curr_day = now->tm_mday;

  int year_diff = year - curr_year;
  int month_diff = month - curr_month;

  if (year_diff == 0) {
    index = month_diff;
  }
  else if (year_diff == 1) {
    index = 12 - curr_month + month;
  }
  else {
    index = 12 - curr_month + 12 + month;
  }

  return index;
}

void master(std::map<int, std::string> *sMap, std::map<int, double> *pMap, std::map<int, double> *rMap, std::vector<request> *requests)
{
    int counter = 0;
    MPI_Status status;
    float sbuf[NUM_IV];
    while(counter < sMap->size()) {
      MPI_Recv(sbuf, NUM_IV, MPI_FLOAT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
        double s, k, r, v,price;
        int t,m,y;
        for(int i = 0; i < requests->size(); i++) {
            if ((*requests)[i].symbol == (*sMap)[status.MPI_TAG]) {
       	      s = (*pMap)[status.MPI_TAG];
    	      k = (*requests)[i].strike;
    	      r = (*rMap)[status.MPI_TAG];
	          m = (*requests)[i].month;
    	      y = (*requests)[i].year;
    	      t = dateDiff(y,m);
    	      price = BlackScholes('c', s, k, (double)t/365 , r, (double)sbuf[hashing(y,m)]);
    	      printf("Result for the request : Symbol : %s Strike : %f Time to Maturity(Days) : %d Price : $%f\n",(*requests)[i].symbol.c_str(),k,t,price);
            }
        }
        counter++;
    }
}

int main(int argc, char **argv) {
  char processor_name[MPI_MAX_PROCESSOR_NAME];
  int len;
  MPI_Init(&argc, &argv);
  MPI_Comm_size(MPI_COMM_WORLD, &procs);
  MPI_Comm_rank(MPI_COMM_WORLD, &procId);
  MPI_Get_processor_name(processor_name, &len);
  init_mpi_solution_type();

  curt = clock();
  std::map<int, double> pMap;
  std::map<int, double> rMap;
  std::map<int, std::string> sMap;

  std::ifstream file("symbol_list.txt");
  std::string symbol;

  CVSRow row;
  int index = 1;
  while(file >> row) {
    if (row[0].c_str() == "")
      continue;

    sMap[index] = row[0].c_str();
    pMap[index] = atof(row[1].c_str());
    rMap[index] = atof(row[2].c_str());
    index++;
  }

  /*
  if (file.is_open()) {
      int index = 1;
      while(!file.eof()){
          getline(file, symbol);
          if (symbol == "") {
              continue;
          }

          sMap[index] = symbol;
          index++;
      }
      file.close();
  }
  else {
    std::cout << "Unable to open file 'symbol_list.txt'\n";
  }
  */
  printf("proc %d :  %s\n",procId,processor_name);
  if(procId == 0)
  {
    std::vector<request> requests;
    extract_requests(&requests);
    master(&sMap,&pMap,&rMap, &requests);
  }
  else {
    printf("Worker!\n");
    //worker(sMap[procId], kMap[procId], pMap[procId]);
    worker(sMap[procId], pMap[procId], rMap[procId]);
  }
  printf("%f\n",(double)(clock() - curt)/CLOCKS_PER_SEC);
  MPI_Finalize();
}
